package net.xdclass.service.impl;

import lombok.extern.slf4j.Slf4j;
import net.xdclass.component.ShortLinkComponent;
import net.xdclass.config.RabbitMQConfig;
import net.xdclass.controller.request.ShortLinkAddRequest;
import net.xdclass.controller.request.ShortLinkDelRequest;
import net.xdclass.controller.request.ShortLinkPageRequest;
import net.xdclass.controller.request.ShortLinkUpdateRequest;
import net.xdclass.enums.DomainTypeEnum;
import net.xdclass.enums.EventMessageType;
import net.xdclass.enums.ShortLinkStateEnum;
import net.xdclass.interceptor.LoginInterceptor;
import net.xdclass.manager.DomainManager;
import net.xdclass.manager.GroupCodeMappingManager;
import net.xdclass.manager.LinkGroupManager;
import net.xdclass.manager.ShortLinkManager;
import net.xdclass.model.*;
import net.xdclass.model.vo.ShortLinkVO;
import net.xdclass.service.ShortLinkService;
import net.xdclass.util.CommonUtil;
import net.xdclass.util.IDUtil;
import net.xdclass.util.JsonData;
import net.xdclass.util.JsonUtil;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;


@Service
@Slf4j
public class ShortLinkServiceImpl implements ShortLinkService {

    @Autowired
    private ShortLinkManager shortLinkManager;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RabbitMQConfig rabbitMQConfig;

    @Autowired
    private DomainManager domainManager;

    @Autowired
    private LinkGroupManager linkGroupManager;

    @Autowired
    private ShortLinkComponent shortLinkComponent;

    @Autowired
    private GroupCodeMappingManager groupCodeMappingManager;

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;


    @Override
    public ShortLinkVO parseShortLinkCode(String shortLinkCode) {
        ShortLinkDO byShortLinCode = shortLinkManager.findByShortLinCode(shortLinkCode);
        if (byShortLinCode == null) {
            return null;
        }
        ShortLinkVO shortLinkVO = new ShortLinkVO();
        BeanUtils.copyProperties(byShortLinCode, shortLinkVO);
        return shortLinkVO;
    }

    @Override
    public JsonData createShortLink(ShortLinkAddRequest request) {

        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        //创建断链码时
        String originalUrl = CommonUtil.addUrlPrefix(request.getOriginalUrl());
        request.setOriginalUrl(originalUrl);

        EventMessage eventMessage = EventMessage.builder().accountNo(accountNo)
                .content(JsonUtil.obj2Json(request))
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageType.SHORT_LINK_ADD.name())
                .build();
        //发送消息
        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(), rabbitMQConfig.getShortLinkAddRoutingKey(), eventMessage);

        return JsonData.buildSuccess();
    }

    /**
     * 短链新增
     *
     * @param eventMessage
     * @return
     */
    @Override
    public boolean handleAddShortLink(EventMessage eventMessage) {
        Boolean duplicateCodeFlag = false;
        Long accountNo = eventMessage.getAccountNo();
        String content = eventMessage.getContent();
        //解析消息体为json
        ShortLinkAddRequest addRequest = JsonUtil.json2Obj(content, ShortLinkAddRequest.class);
        //判断短链域名是否合法
        DomainDO domainDO = this.checkDomain(addRequest.getDomainType(), addRequest.getDomainId(), accountNo);
        //判断组名是否合法
        LinkGroupDO linkGroupDO = this.checkLinkGroup(addRequest.getGroupId(), accountNo);
        //生成长链摘要
        String originalUrl = addRequest.getOriginalUrl();
        String originalUrlDigest = CommonUtil.MD5(originalUrl);
        //生成短链码
        String shortLinkCode = shortLinkComponent.createShortLinkCode(originalUrl);

        // 加锁  先加锁再查  防止查到没有  去存的时候刚刚好有线程添加成功 导致当前线程添加失败
        //key1是短链码，ARGV[1]是accountNo,ARGV[2]是过期时间
        String script = "if redis.call('EXISTS',KEYS[1])==0 then redis.call('set',KEYS[1],ARGV[1]); redis.call('expire',KEYS[1],ARGV[2]); return 1;" +
                " elseif redis.call('get',KEYS[1]) == ARGV[1] then return 2;" +
                " else return 0; end;";

        Long result = redisTemplate.execute(new
                DefaultRedisScript<>(script, Long.class), Arrays.asList(shortLinkCode), accountNo, 100);
        if (result > 0) {
            //加锁成功
            //构建短链对象
            if (eventMessage.getEventMessageType().equalsIgnoreCase(EventMessageType.SHORT_LINK_ADD_LINK.name())) {
                //查询断链码是否被占用
                ShortLinkDO shortLinCodeInDB = shortLinkManager.findByShortLinCode(shortLinkCode);
                if (shortLinCodeInDB == null) {
                    ShortLinkDO build = ShortLinkDO.builder()
                            .accountNo(accountNo)
                            .code(shortLinkCode)
                            .title(addRequest.getTitle())
                            .originalUrl(originalUrl)
                            .domain(domainDO.getValue())
                            .groupId(linkGroupDO.getId())
                            .expired(addRequest.getExpired())
                            .sign(originalUrlDigest)
                            .state(ShortLinkStateEnum.ACTIVCE.name())
                            .del(0)
                            .build();
                    shortLinkManager.addShortLink(build);
                    return true;
                } else {
                    log.error("C端断链码重复{}" + eventMessage);
                    duplicateCodeFlag = true;
                }

            } else if (eventMessage.getEventMessageType().equalsIgnoreCase(EventMessageType.SHORT_LINK_ADD_MAPPING.name())) {
                GroupCodeMappingDO groupCodeMappingInDB = groupCodeMappingManager.findByCodeAndGroupID(shortLinkCode, linkGroupDO.getId(), accountNo);
                if (groupCodeMappingInDB == null) {
                    GroupCodeMappingDO build = GroupCodeMappingDO.builder()
                            .accountNo(accountNo)
                            .code(shortLinkCode)
                            .title(addRequest.getTitle())
                            .originalUrl(originalUrl)
                            .domain(domainDO.getValue())
                            .groupId(linkGroupDO.getId())
                            .expired(addRequest.getExpired())
                            .sign(originalUrlDigest)
                            .state(ShortLinkStateEnum.ACTIVCE.name())
                            .del(0)
                            .build();
                    groupCodeMappingManager.add(build);
                    return true;
                } else {
                    log.error("B端断链码重复{}" + eventMessage);
                    duplicateCodeFlag = true;
                }

            }

        } else {
            //加锁失败  自旋100毫秒  再调用
            log.error("加锁失败{}" + eventMessage);
            try {
                TimeUnit.MICROSECONDS.sleep(100);
            } catch (InterruptedException e) {
            }
            duplicateCodeFlag = true;
        }
        //自旋
        if (duplicateCodeFlag) {
            String newOriginalUrl = CommonUtil.addUrlPrefixVersion(addRequest.getOriginalUrl());
            addRequest.setOriginalUrl(newOriginalUrl);
            eventMessage.setContent(JsonUtil.obj2Json(addRequest));
            log.warn("重新生成断链码");
            this.handleAddShortLink(eventMessage);
        }

        return false;

    }

    @Override
    public boolean handleUpdateShortLink(EventMessage eventMessage) {
        String content = eventMessage.getContent();
        Long accountNo = eventMessage.getAccountNo();
        ShortLinkUpdateRequest request = JsonUtil.json2Obj(content, ShortLinkUpdateRequest.class);
        //参数校验
        DomainDO domainDO = checkDomain(request.getDomainType(), request.getDomainId(), accountNo);
        if (eventMessage.getEventMessageType().equalsIgnoreCase(EventMessageType.SHORT_LINK_UPDATE_LINK.name())) {
            //c端

            ShortLinkDO shortLinkDO = ShortLinkDO.builder().accountNo(accountNo).title(request.getTitle()).domain(domainDO.getValue()).code(request.getCode()).build();
            int rows = shortLinkManager.update(shortLinkDO);
            log.debug("更新b端短链{}", rows);
            return true;
        } else if (eventMessage.getEventMessageType().equalsIgnoreCase(EventMessageType.SHORT_LINK_UPDATE_MAPPING.name())) {
            //处理B端

            GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder().accountNo(accountNo).groupId(request.getGroupId()).domain(domainDO.getValue())
                    .id(request.getMappingId()).title(request.getTitle()).build();
            int rows = groupCodeMappingManager.update(groupCodeMappingDO);
            log.debug("更新b端短链{}", rows);
            return true;
        }

        return false;
    }

    @Override
    public boolean handleDelShortLink(EventMessage eventMessage) {
        Long accountNo = eventMessage.getAccountNo();
        String content = eventMessage.getContent();
        //json转成对象
        ShortLinkDelRequest request = JsonUtil.json2Obj(content, ShortLinkDelRequest.class);
        String eventMessageType = eventMessage.getEventMessageType();
        if (EventMessageType.SHORT_LINK_DEL_LINK.name().equalsIgnoreCase(eventMessageType)) {
            //C端处理
            ShortLinkDO shortLinkDO = ShortLinkDO.builder().accountNo(accountNo).code(request.getCode()).build();
            int rows = shortLinkManager.del(shortLinkDO);
            log.debug("删除c端{}", rows);
            return true;

        } else if (EventMessageType.SHORT_LINK_DEL_MAPPING.name().equalsIgnoreCase(eventMessageType)) {
            //B端处理
            GroupCodeMappingDO groupCodeMappingDO = GroupCodeMappingDO.builder()
                    .id(request.getMappingId())
                    .accountNo(accountNo)
                    .groupId(request.getGroupId()).build();
            int rows = groupCodeMappingManager.del(groupCodeMappingDO);
            log.debug("删除b端{}", rows);
            return true;
        }
        return false;
    }

    @Override
    public Map<String, Object> pageShortLinkByGroupId(ShortLinkPageRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();
        return groupCodeMappingManager.pageShortLinkByGroupId(request.getPage(), request.getSize(), accountNo, request.getGroupId());
    }

    @Override
    public JsonData del(ShortLinkDelRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        EventMessage eventMessage = EventMessage.builder().accountNo(accountNo)
                .content(JsonUtil.obj2Json(request))
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageType.SHORT_LINK_DEL.name())
                .build();
        // 发送消息
        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(), rabbitMQConfig.getShortLinkDelRoutingKey(), eventMessage);
        return JsonData.buildSuccess();
    }

    @Override
    public JsonData update(ShortLinkUpdateRequest request) {
        Long accountNo = LoginInterceptor.threadLocal.get().getAccountNo();

        EventMessage eventMessage = EventMessage.builder().accountNo(accountNo)
                .content(JsonUtil.obj2Json(request))
                .messageId(IDUtil.geneSnowFlakeID().toString())
                .eventMessageType(EventMessageType.SHORT_LINK_UPDATE.name())
                .build();
        // 发送消息
        rabbitTemplate.convertAndSend(rabbitMQConfig.getShortLinkEventExchange(), rabbitMQConfig.getShortLinkUpdateRoutingKey(), eventMessage);
        return JsonData.buildSuccess();
    }

    /**
     * 校验短链组
     */
    private LinkGroupDO checkLinkGroup(Long groupId, Long accountNo) {
        LinkGroupDO linkGroupDO = linkGroupManager.detail(groupId, accountNo);
        Assert.notNull(linkGroupDO, "短链组不合法");
        return linkGroupDO;
    }

    /**
     * 检验domain合法
     *
     * @param domainType
     * @param domainId
     * @param accountNo
     * @return
     */
    private DomainDO checkDomain(String domainType, Long domainId, Long accountNo) {
        DomainDO domainDO;
        if (domainType.equalsIgnoreCase(DomainTypeEnum.CUSTOM.name())) {
            //自建
            domainDO = domainManager.findById(domainId, accountNo);
        } else {
            //官方
            domainDO = domainManager.findByDomainTypeAndID(domainId, DomainTypeEnum.OFFICIAL);
        }
        Assert.notNull(domainDO, "domain不合法");
        return domainDO;
    }
}
